/*
 * server_request_handler.cpp
 *
 *	Ex: 5
 *
 *  Created on: Jun 8, 2012
 *      Author: Ariel Ayash
 */


#include "server.h"
#include "protocol.h"

#include <time.h>
#include <cstdlib>
#include <iostream>
#include <netinet/in.h>
#include <cassert>
#include <vector>
#include <cstring>
#include <algorithm>

using namespace std;

//	------------------------------------------------------------->
//				Utilities functions
//	------------------------------------------------------------->

static void get_time (char *str_time)
{
	// get the current time
	time_t tim = time( NULL );
	char s[26];
	ctime_r( & tim, s );

	// remove the needed fields
	memcpy( str_time, & s[11], 5 );
	str_time[5] = '\0';
}

void Server::send_msg( uint client_socket,
					   messages_type_t type,
					   response_status_t status,
					   string & buffer )
{
	deferred_msg_t def_msg;
	uint msg_size;
	uint payload_length;

	def_msg.socket = client_socket;

	if ( buffer.length() )
	{
		payload_length = buffer.length() + 1; // +1 for terminating null-character
	}
	else
	{
		payload_length = 0;
	}

	msg_size = MSG_METADATA_LENGTH + payload_length;

	def_msg.msg = (out_msg_t *) malloc (msg_size);
	if ( NULL == def_msg.msg )
	{
		print_err_msg( "Error: couldn't allocate a buffer for message" );
		return;
	}

	def_msg.msg->header.type = type;
	def_msg.msg->header.payload_length = payload_length;
	def_msg.msg->header.status = status;
	def_msg.first_time = true;
	def_msg.already_sent = 0;

	if ( payload_length )
	{
		memcpy( def_msg.msg->payload, buffer.c_str(), payload_length );
	}

	_deferred_msg.push_back( def_msg );

}

int Server::get_socket_by_name( const char *name )
{
	map<const char *, uint>::iterator it = _names.find( name );

	if ( it == _names.end() )
	{
		// no socket match the given name
		return INVALID_SOCKET;
	}

	return it->second;
}

ClientContext* Server::get_context_by_socket( uint client_socket  )
{
	map< uint, ClientContext* >::iterator it = _clients.find( client_socket );

	if ( it == _clients.end() )
	{
		// no context match the given socket number
		return NULL;
	}

	return it->second;
}


//	------------------------------------------------------------->

result_t Server::parse_message( uint client_socket, in_msg_t *msg )
{
	char *end_name;

	switch( msg->header.type ) {

	case MESSAGE_TYPE_CONNECT:

		set_new_client_name( client_socket, msg->payload );

		break;

	case MESSAGE_TYPE_DISCONNECT:

		close_connection_with_client( client_socket, true );

		return REMOVE_CLIENT;

	case MESSAGE_TYPE_FOLLOW:

		add_to_follow_list( client_socket, msg->payload );

		break;

	case MESSAGE_TYPE_UNFOLLOW:

		remove_from_follow_list( client_socket, msg->payload );

		break;

	case MESSAGE_TYPE_TWIT:

		twit( client_socket, msg->payload );

		break;

	case MESSAGE_TYPE_DIRECT_MESSAGE:

		end_name = strchr( msg->payload, '@' );
		assert( NULL != end_name );

		*end_name = '\0';

		send_direct_msg( client_socket, msg->payload, end_name + 1 );

		break;

	case MESSAGE_TYPE_PUBLIC_ANNOUNCEMENT:

		send_public_announcement( client_socket, msg->payload );

		break;

	case MESSAGE_TYPE_WHO:

		send_clients_list( client_socket );

		break;

	default:

		ERR_PRINT("Unknown message type was received!\n");
		assert(0);

		break;
	}

	return OPERATION_SUCCESS;
}


void Server::set_new_client_name( uint client_socket, const char *name )
{
	messages_type_t type = MESSAGE_TYPE_CONNECT_RESPONSE;
	response_status_t status;
	string buffer;

	char tmp_name[MAXIMAL_CLIENT_NAME_LENGTH];

	strncpy(tmp_name, name, MAXIMAL_CLIENT_NAME_LENGTH );

	// print log message
	_log_file << tmp_name << "\tconnected." << endl;

	ClientContext *context = get_context_by_socket( client_socket );

	assert( NULL != context );

	context->set_client_name( tmp_name );

	pair< map<const char *, uint>::iterator, bool > ret;

	ret = _names.insert( pair<const char*, uint>(context->get_client_name(), client_socket) );

	if ( ret.second )
	{
		// There is no other client with the given name
		status = RESPONSE_STATUS_SUCCESS;
	}
	else
	{
		// We already have a client with the given name
		status = RESPONSE_STATUS_ERROR;

		_log_file << "Error " << tmp_name << " already in use" << endl;

		context->set_client_name("\0");
	}

	send_msg( client_socket, type, status, buffer );
}

void Server::close_connection_with_client( uint client_socket, bool explicit_close )
{
	ClientContext *context = get_context_by_socket( client_socket );

	assert( NULL != context );

	const char *name = context->get_client_name();

	if ( strlen(name) )
	{
		// print log message
		if ( explicit_close )
		{
			_log_file <<  name << "\tdisconnected." << endl;
		}
		else
		{
			_log_file << "Error connection with " << name << " terminated" << endl;
		}

		_names.erase( name );
	}


	// remove the client from the context of the clients that follows him
	if ( context->is_someone_follows() )
	{
		// make sure the client doesn't follows himself
		context->remove_from_followed_list( client_socket );

		vector< ClientContext*  > followers_list;
		context->get_followers_list( followers_list );

		vector< ClientContext*  >::iterator it;
		for ( it = followers_list.begin(); it != followers_list.end(); ++it )
		{
			(*it)->remove_from_followed_list( client_socket );
		}
	}


	// remove the client from the context of the clients that he follows
	if ( ! context->is_followed_empty() )
	{
		// the client follows some clients

		// make sure the client doesn't follows himself
		context->remove_from_followers_list( client_socket );

		vector< ClientContext*  > followed_list;
		context->get_followed_list( followed_list );

		vector< ClientContext*  >::iterator it;
		for ( it = followed_list.begin(); it != followed_list.end(); ++it )
		{
			(*it)->remove_from_followers_list( client_socket );
		}

		//context->clear_followed_list();
	}


	/*
	 * remove the client from the set of sockets so we won't
	 * wait for other messages from his socket.
	 */
	FD_CLR( client_socket, & _in_set);

	// remove any deferred messages that waits for this client
	list< deferred_msg_t >::iterator  msg_it;

	for ( msg_it = _deferred_msg.begin(); msg_it != _deferred_msg.end(); ++msg_it )
	{
		deferred_msg_t tmp = *msg_it;
		if ( tmp.socket == client_socket )
		{
			free ( tmp.msg );

			msg_it = _deferred_msg.erase( msg_it );
		}
	}

	if ( shutdown( client_socket, SHUT_RDWR ) || close ( client_socket )  )
	{
		ERR_PRINT("Error close() on client's socket failed!\n");
		print_err_msg( "Error closing server socket failed", true );
	}

	DBG_PRINT("deleting socket %u\n", client_socket);
	delete context;
}

void Server::add_to_follow_list( uint client_socket, const char *name_followed )
{
	ClientContext *context = get_context_by_socket( client_socket );

	assert( NULL != context );

	char tmp_name[MAXIMAL_CLIENT_NAME_LENGTH];

	strncpy(tmp_name, name_followed, MAXIMAL_CLIENT_NAME_LENGTH );

	const string followed_name(tmp_name);

	// print log message
	_log_file <<  context->get_client_name() << "\tfollowed "
												<< followed_name << "." << endl;

	int followed_socket = get_socket_by_name( tmp_name );

	if ( INVALID_SOCKET == followed_socket )
	{
		// unknown client to follow

		messages_type_t type = MESSAGE_TYPE_FOLLOW_RESPONSE;
		response_status_t status = RESPONSE_STATUS_ERROR;

		// the given client name doesn't exists
		string buffer ( "Error: " + followed_name + " does not exist" );

		// print the error message to the log file
		_log_file << buffer << endl;

		send_msg( client_socket, type, status, buffer);
	}
	else
	{
		// mark the client in the context of the client that he requested to follow
		ClientContext *followed_context = get_context_by_socket( followed_socket );
		assert( NULL != followed_context );

		followed_context->add_to_followers_list( client_socket, context );

		context->add_to_followed_list( followed_socket, followed_context );
	}
}

void Server::remove_from_follow_list( uint client_socket, const char *name_followed )
{
	ClientContext *context = get_context_by_socket( client_socket );

	assert( NULL != context );

	char tmp_name[MAXIMAL_CLIENT_NAME_LENGTH];

	strncpy(tmp_name, name_followed, MAXIMAL_CLIENT_NAME_LENGTH );

	const string followed_name(tmp_name);

	// print log message
	_log_file << context->get_client_name() << "\tunfollowed "
												<< followed_name << "." << endl;

	int followed_socket = get_socket_by_name( tmp_name );

	if ( INVALID_SOCKET == followed_socket )
	{
		messages_type_t type = MESSAGE_TYPE_UNFOLLOW_RESPONSE;
		response_status_t status = RESPONSE_STATUS_ERROR;
		// the given client name doesn't exists
		string buffer ( "Error: " + followed_name + " does not exist" );

		// print the error message to the log file
		_log_file << buffer << endl;

		send_msg( client_socket, type, status, buffer);
	}
	else
	{
		// add the client to the followers list
		ClientContext *followed_context = get_context_by_socket( followed_socket );
		assert( NULL != followed_context );

		followed_context->remove_from_followers_list( client_socket );
		context->remove_from_followed_list( followed_socket );
	}
}

void Server::twit( uint client_socket, const char *msg )
{
	messages_type_t type = MESSAGE_TYPE_TWIT_RESPONSE;
	response_status_t status = RESPONSE_STATUS_SUCCESS;

	ClientContext *context = get_context_by_socket( client_socket );
	assert( NULL != context );

	const string client_name( context->get_client_name() );
	const string str_msg(msg);

	// print log message
	_log_file << client_name <<	"\ttwitted " << str_msg << "." << endl;

	// get time as a string
	char str_time[6];
	get_time(str_time);

	string buffer( string(str_time) + "::" + client_name + " - " + str_msg );

	vector<uint> followers_sockets;

	context->get_client_followers_socket( followers_sockets );

	vector<uint>::iterator it;
	for ( it = followers_sockets.begin(); it != followers_sockets.end(); ++it )
	{
		send_msg( *it, type, status, buffer );
	}
}

void Server::send_direct_msg( uint client_socket,
					  	  	  const char *recipient_name,
					  	  	  const char *msg )
{
	messages_type_t type = MESSAGE_TYPE_DIRECT_MESSAGE_RESPONSE;
	response_status_t status;
	string buffer;

	// print log message

	ClientContext *context = get_context_by_socket( client_socket );

	assert( NULL != context );

	char tmp_name[MAXIMAL_CLIENT_NAME_LENGTH];

	strncpy(tmp_name, recipient_name, MAXIMAL_CLIENT_NAME_LENGTH );

	const string client_name ( context->get_client_name() );
	const string str_recipient_name ( tmp_name );
	const string str_msg( msg );

	// print message to log file
	_log_file << client_name << "\tdirect messaged " << str_recipient_name
													<< "\t " << str_msg << "." << endl;


	int followed_socket = get_socket_by_name( tmp_name );
	if ( INVALID_SOCKET == followed_socket )
	{
		status = RESPONSE_STATUS_ERROR;
		// the given client name doesn't exists
		buffer =  ( "Error: " + str_recipient_name + " does not exist" );

		// print the error message to the log file
		_log_file << buffer << endl;

		send_msg( client_socket, type, status, buffer);
	}
	else
	{
		status = RESPONSE_STATUS_SUCCESS;

		// get time as a string
		char str_time[6];
		get_time( str_time );

		buffer = ( string(str_time) + "::" + client_name + "  @" + str_recipient_name
																	   + "--" + str_msg );

		send_msg( followed_socket, type, status, buffer);

	}

}

void Server::send_public_announcement( uint client_socket, const char *msg )
{
	messages_type_t type = MESSAGE_TYPE_PUBLIC_ANNOUNCEMENT_RESPONSE;
	response_status_t status = RESPONSE_STATUS_SUCCESS;

	ClientContext *context = get_context_by_socket( client_socket );
	assert( NULL != context );

	const string client_name( context->get_client_name() );
	const string str_msg(msg);

	// print log message
	_log_file << client_name <<	"\tannounced " << str_msg << "." << endl;

	// get time as a string
	char str_time[6];
	get_time(str_time);

	string buffer( string(str_time) + "::" + client_name + " - " + str_msg );

	map< uint, ClientContext* >::iterator it;
	for ( it = _clients.begin(); it != _clients.end(); ++it )
	{
		send_msg( it->first, type, status, buffer );
	}
}

void Server::send_clients_list( uint client_socket )
{
	messages_type_t type = MESSAGE_TYPE_WHO_RESPONSE;
	response_status_t status = RESPONSE_STATUS_SUCCESS;
	string buffer;

	ClientContext *context = get_context_by_socket( client_socket );
	assert( NULL != context );

	// print log message
	_log_file << context->get_client_name() <<	"\tWho." << endl;

	vector<const char*> clients_list;

	clients_list.reserve( _names.size() );

	// enter all the known clients to the list
	std::map<const char *, uint , NamesCmp>::iterator it;
	for ( it = _names.begin(); it != _names.end(); ++it )
	{
		clients_list.push_back( it->first );
	}

	NamesCmp cmp;

	// sort the names in the list
	sort( clients_list.begin(), clients_list.end(), cmp );

	// prepare the message that should be sent to the requesting client

	vector<const char*>::iterator names_it = clients_list.begin();

	// there should be at least one name in the list
	buffer = *names_it ;

	for ( ++names_it; names_it != clients_list.end(); ++names_it )
	{
		buffer.append("\t ");
		buffer.append( *names_it );
	}

	send_msg( client_socket, type, status, buffer );
}
